package com.jl.cloud.commom.aspect;



import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Stack;

/**
 * 日志拦截
 * @author liang.jun
 */
@Component
@Aspect
@Slf4j
public class LogAspect {

    // 保存controller方法开始执行时间
    private final ThreadLocal<Long> tlTimeCtl = new ThreadLocal<>();
    // 保存service方法开始执行时间，因为存在service嵌套调用，采用栈存储
    private final ThreadLocal<Stack<Long>> tlTimeSvc = new ThreadLocal<>();
    // 保存方法请求时间
    private final ThreadLocal<String> tlReqTime = new ThreadLocal<>();
    // 保存方法请求路径
    private final ThreadLocal<String> tlUrl = new ThreadLocal<>();
    // 保存方法请求入参
    private final ThreadLocal<String> tlParam = new ThreadLocal<>();

    // 拦截所有的控制层和服务层
    @Pointcut("!@annotation(com.jl.cloud.commom.aspect.SkipLog) && (execution(public * com.jl..*.controller..*.*(..))))")
    public void pointcut() {}

    @Before("pointcut()")
    public void doLogBefore(JoinPoint jp) {
        try {
            Object[] args = jp.getArgs();
            MethodSignature methodSignature = (MethodSignature)jp.getSignature();
            String[] methodParameterNames = methodSignature.getParameterNames();
            StringBuilder params = new StringBuilder();
            HttpServletRequest request = null;

            // 如果是controller层，获取request对象
            if (jp.getTarget().getClass().getName().contains("controller")) {
                request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
            }

            // 处理request参数
            boolean hasRequest = false;
            for (Object arg : args) {
                if (arg instanceof HttpServletRequest) {
                    if (!request.getParameterMap().isEmpty()) {
                        params.append(
                                JSON.toJSONString(request.getParameterMap(), SerializerFeature.WriteDateUseDateFormat)
                                        .replaceAll("\\[", "").replaceAll("\\]", ""));
                        hasRequest = true;
                    } else if (request.getContentLength() > 0) {
                        params.append("[requestBody-length:").append(request.getContentLength()).append("]");
                    }
                    break;
                }
            }

            // 如果有request参数了，则其他的参数不再重复打印
            for (int i = 0; !hasRequest && i < args.length; i++) {
                // 这些参数不处理
                if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse
                        || args[i] instanceof MultipartFile || args[i] instanceof HttpSession) {
                    continue;
                }

                // base64字段只打印前100个字符
                if (args[i] instanceof String && args[i].toString().startsWith("data:image/png;base64")) {
                    args[i] = args[i].toString().substring(1, 100) + " ... (图片字段太长)";
                }

                String param = JSON.toJSONString(args[i], SerializerFeature.WriteDateUseDateFormat);



                params.append(params.length() == 0 ? "" : ",");
                if (methodParameterNames.length > i) {
                    params.append("\"").append(methodParameterNames[i]).append("\":").append(param);
                } else {
                    params.append(param);
                }
            }
            if (!params.toString().startsWith("{")) {
                params.insert(0, "{").append("}");
            }

            String cmName = jp.getTarget().getClass().getName() + "." + methodSignature.getName();
            if (jp.getTarget().getClass().getName().contains("controller")) {

                // 记录接口请求时间
                tlReqTime.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

                String requestUrl = request.getRequestURI().replaceAll(request.getContextPath(), "");

                log.info("=============接口调用开始，请求路径： {}=============\n 方法名： {}\n 入参：  {}\n", requestUrl,
                        cmName, subStr(params.toString()));

                // 记录方法的请求路径和入参
                tlUrl.set(requestUrl);
                tlParam.set(params.toString());
            } else {
                log.info("-------------服务调用开始-------------\n 方法名： {}\n 入参： {}\n", cmName,
                        subStr(params.toString()));
            }

            // 记录方法开始执行的时间戳
            if (jp.getTarget().getClass().getName().contains("controller")) {
                tlTimeCtl.set(System.currentTimeMillis());
            } else {
                if (tlTimeSvc.get() == null) {
                    Stack<Long> stack = new Stack<>();
                    stack.push(System.currentTimeMillis());
                    tlTimeSvc.set(stack);
                } else {
                    tlTimeSvc.get().push(System.currentTimeMillis());
                }
            }
        } catch (Exception e) {
            log.error("日志切面发生异常：", e);
        }
    }

    @AfterReturning(pointcut = "pointcut()", returning = "result")
    public void doLogAfter(JoinPoint jp, Object result) {
        try {
            String cmName = jp.getTarget().getClass().getName() + "." + jp.getSignature().getName();

            // json序列化（日期格式采用默认的 yyyy-MM-dd HH:mm:ss）
            String jsonResult = JSON.toJSONString(result, SerializerFeature.WriteDateUseDateFormat);

            if (jp.getTarget().getClass().getName().contains("controller")) {
                // 计算方法执行的耗时
                long timec = System.currentTimeMillis() - tlTimeCtl.get();
                // 如果执行时间过短，睡眠1ms，防止日志顺序错乱
                if (timec == 0) {
                    Thread.sleep(1L);
                }
                log.info(
                        "=============接口调用结束，请求时间【{}】，请求路径【{}】，耗时【{}】毫秒=============\n 方法名： {}\n 入参： {}\n 出参：  {}\n",
                        tlReqTime.get(), tlUrl.get(), (timec == 0 ? "< 1" : timec), cmName, tlParam.get(),
                        subStr(jsonResult));

                // 释放threadlocal，避免内存泄露
                cleanThreadLocal();
            } else {
                long times = System.currentTimeMillis() - tlTimeSvc.get().pop();
                if (times == 0) {
                    Thread.sleep(1L);
                }
                log.debug("-------------服务调用成功，耗时 【 {} 】毫秒-------------\n 方法名： {}\n 出参：  {}\n",
                        (times == 0 ? "< 1" : times), cmName, subStr(jsonResult));

                // 释放threadlocal，避免内存泄露
                if (tlTimeSvc.get().isEmpty()) {
                    tlTimeSvc.remove();
                }
            }

        } catch (Exception e) {
            log.error("日志切面发生异常：", e);
        }
    }

    @AfterThrowing(pointcut = "pointcut()", throwing = "ex")
    public void doLogThrowing(JoinPoint jp, Throwable ex) {
        try {
            String cmName = jp.getTarget().getClass().getName() + "." + jp.getSignature().getName();

            // 不是controller层，一般是服务层方法
            if (!jp.getTarget().getClass().getName().contains("controller")) {
                long times = System.currentTimeMillis() - tlTimeSvc.get().pop();
                log.debug("-------------服务调用异常，耗时 【 {} 】毫秒：", (times == 0 ? "< 1" : times), ex);

                // 释放threadlocal，避免内存泄露
                if (tlTimeSvc.get().isEmpty()) {
                    tlTimeSvc.remove();
                }
            } else {
                long timec = System.currentTimeMillis() - tlTimeCtl.get();
                log.info(
                        "=============接口调用结束，请求时间：【{}】，请求路径： 【{}】，耗时 【 {} 】毫秒=============\n 方法名： {}\n 入参：  {}\n 异常信息： {} ",
                        tlReqTime.get(), tlUrl.get(), (timec == 0 ? "< 1" : timec), cmName, tlParam.get(), ex.getMessage());

                // 释放threadlocal，避免内存泄露
                cleanThreadLocal();
            }

        } catch (Exception e) {
            log.error("日志切面发生异常：", e);
        }
    }

    /**
     * 字符串太长，进行截取(暂定100000个字符，大概100k左右)
     *
     * @param source
     * @return
     */
    private String subStr(String source) {
        int length = 100000;
        if (source == null) {
            return null;
        } else if (source.length() <= length) {
            return source;
        } else {
            return source.substring(0, length) + " ...... ( log is too long ~ ) ";
        }
    }

    /**
     * 清理threadlocal变量，防止内存泄露
     *
     */
    private void cleanThreadLocal() {
        tlTimeCtl.remove();
        tlTimeSvc.remove();
        tlReqTime.remove();
        tlUrl.remove();
        tlParam.remove();
    }

}
